package com.ibeeking.gateway.cp.config.filter;

import com.alibaba.fastjson.JSONObject;
import com.ibeeking.gateway.cp.constant.LogUtils;
import com.ibeeking.gateway.cp.constant.RedisKeyConstant;
import com.ibeeking.gateway.cp.constant.ThreadContextCache;
import com.ibeeking.found.common.config.feign.GlobalClient;
import com.ibeeking.found.common.constants.HeaderNounConstant;
import com.ibeeking.found.common.entity.open.OpenAuthDTO;
import com.ibeeking.found.common.entity.open.OpenAuthQuery;
import com.ibeeking.found.common.enums.LoginRoleEnum;
import com.ibeeking.found.common.response.ResponseCodeEnum;
import com.ibeeking.found.common.utils.DataUtils;
import com.ibeeking.found.system.api.common.param.RefreshTokenParam;
import com.ibeeking.found.system.api.feign.UaaCpClient;
import com.ibeeking.nematos.constants.enums.LogConstant;
import com.ibeeking.nematos.redis.utils.RedisUtils;
import com.ibeeking.nematos.utils.data.Collections3;
import com.ibeeking.nematos.utils.data.StringUtils;
import com.ibeeking.nematos.utils.json.JsonUtils;
import com.ibeeking.nematos.utils.result.ResponseResult;
import com.nimbusds.jose.JWSObject;
import lombok.SneakyThrows;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.List;

/**
 * 黑名单token过滤器
 *
 * @Author ibeeking
 */
@Component
@RefreshScope
public class GlobalFilter implements org.springframework.cloud.gateway.filter.GlobalFilter, Ordered {
    private static final String APP_PREFIX = "CP_APPID";

    @Resource
    private UaaCpClient uaaCpClient;

    @Resource
    private GlobalClient globalClient;

    @Resource
    private RedisUtils redisUtils;

    @Value("${ibeeking.tokenExpires:1800}")
    private Long tokenExpires;

    @SneakyThrows
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        //解析token
        JSONObject tokenObject = analysisToken(request.getHeaders().getFirst(HeaderNounConstant.AUTHORIZATION));
        ServerHttpRequest newRequest = addHeader(request, tokenObject);
        try {
            String newToken = refreshToken(tokenObject);
            if (HttpStatus.UNAUTHORIZED.getReasonPhrase().equals(newToken)) {
                ServerHttpResponse response = exchange.getResponse();
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            } else if (StringUtils.isNotBlank(newToken)) {
                ServerHttpResponse response = exchange.getResponse();
                response.getHeaders().add(HeaderNounConstant.AUTHORIZATION, newToken);
                return chain.filter(exchange.mutate().request(newRequest).response(response).build());
            }
            return chain.filter(exchange.mutate().request(newRequest).build());
        } finally {
            ThreadContext.clearAll();
        }
    }

    private String refreshToken(JSONObject tokenObject) throws ParseException {
        if (null == tokenObject) {
            return "";
        }
        Boolean register = (Boolean) tokenObject.get(HeaderNounConstant.REGISTER);
        if (register) {
            return HttpStatus.UNAUTHORIZED.getReasonPhrase();
        }
        //判断时间是否达到超时时间
        Long expTime = Long.valueOf(tokenObject.get("exp").toString());
        if (expTime * 1000 - System.currentTimeMillis() <= tokenExpires * 1000) {
            //刷新token
            String tokenId = tokenObject.get(HeaderNounConstant.JWT_JTI).toString().replaceAll("-", "");
            RefreshTokenParam refreshTokenParam = new RefreshTokenParam();
            refreshTokenParam.setClientId(tokenObject.get(HeaderNounConstant.CLIENT_ID).toString());
            refreshTokenParam.setJti(tokenId);
            ResponseResult<String> responseResult = uaaCpClient.refreshToken(refreshTokenParam);
            if (StringUtils.isNotBlank(responseResult.getData())) {
                return responseResult.getData();
            }
        }
        return "";
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

    /**
     * 添加请求头信息
     *
     * @param request
     * @return
     * @throws ParseException
     */
    private ServerHttpRequest addHeader(ServerHttpRequest request, JSONObject tokenObject) throws ParseException {
        ServerHttpRequest build = request.mutate().build();
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(request.getHeaders());
        headers.add(LogConstant.TRACE_ID, request.getId());
        String industry;
        String userId;
        String userName;
        String amDemoUser;
        String tokenId;
        String loginSystem;
        String loginRole;
        headers.remove(HeaderNounConstant.TENANT_ID);
        if (null != tokenObject) {
            industry = tokenObject.get(HeaderNounConstant.INDUSTRY).toString();
            userId = tokenObject.get(HeaderNounConstant.USER_ID).toString();
            userName = null == tokenObject.get(HeaderNounConstant.USER_NAME) ? "" : tokenObject.get(HeaderNounConstant.USER_NAME).toString();
            amDemoUser = tokenObject.get(HeaderNounConstant.AM_DEMO_USER).toString();
            tokenId = tokenObject.get(HeaderNounConstant.JWT_JTI).toString().replaceAll("-", "");
            headers.add(HeaderNounConstant.TENANT_ID, tokenObject.get(HeaderNounConstant.TENANT_ID).toString());
            List<String> roles = (List<String>) tokenObject.get(HeaderNounConstant.AUTHORITY_CLAIM_NAME);
            loginSystem = roles.get(0);
            if (roles.size() > 1) {
                loginRole = roles.get(1);
            } else {
                loginRole = roles.get(0);
            }
        } else {
            industry ="";
            userId = "";
            userName = "";
            amDemoUser = "false";
            tokenId = "";
            loginSystem = LoginRoleEnum.WX_CP.name();
            loginRole = LoginRoleEnum.WX_CP.name();
            List<String> wxAppIds = headers.get(HeaderNounConstant.APP_ID);
            if (!Collections3.isEmpty(wxAppIds)) {
                Long tenantId = queryTenantId(wxAppIds.get(0));
                headers.add(HeaderNounConstant.TENANT_ID,tenantId.toString());
            }
        }
        //headers.remove(HeaderNounConstant.WX_APP_ID);
        headers.add(HeaderNounConstant.USER_ID, userId);
        headers.add(HeaderNounConstant.LOGIN_SYSTEM, loginSystem);
        headers.add(HeaderNounConstant.LOGIN_ROLE, loginRole);
        headers.add(HeaderNounConstant.INDUSTRY, industry);
        headers.add(HeaderNounConstant.AM_DEMO_USER, amDemoUser);
        try {
            headers.add(HeaderNounConstant.USER_NAME, URLEncoder.encode(userName, StandardCharsets.UTF_8.name()));
        } catch (UnsupportedEncodingException e) {
            LogUtils.error("中文编码异常[{}]", userName);
        }
        headers.add(HeaderNounConstant.JWT_JTI, tokenId);
        LogUtils.info("headers>>>>>>={}", JsonUtils.toJsonStr(headers));
        //往本地线程添加信息
        ThreadContextCache.threadContext(build.getId(),
                build.getHeaders().getFirst(HeaderNounConstant.TENANT_ID),
                build.getHeaders().getFirst(HeaderNounConstant.APP_ID),
                build.getHeaders().getFirst(HeaderNounConstant.USER_NAME),
                build.getHeaders().getFirst(HeaderNounConstant.USER_ID),
                build.getHeaders().getFirst(HeaderNounConstant.AM_DEMO_USER),
                industry
        );

        return new ServerHttpRequestDecorator(build) {
            @Override
            public HttpHeaders getHeaders() {
                return headers;
            }
        };
    }

    /**
     * 解析token
     *
     * @param token token
     * @return
     * @throws ParseException
     */
    private JSONObject analysisToken(String token) throws ParseException {
        JSONObject jsonObject = null;
        if (StringUtils.isNotBlank(token)) {
            String payload = JWSObject.parse(token.replace(HeaderNounConstant.JWT_PREFIX, Strings.EMPTY)).getPayload().toString();
            jsonObject = JSONObject.parseObject(payload);
        }
        return jsonObject;
    }

    /**
     * 通过APPId从Global中读取租户ID
     *
     * @param appId token
     * @return
     * @throws ParseException
     */
    private  Long queryTenantId(String appId) {
        if (StringUtils.isBlank(appId)) {
            return 0L;
        }
        Object tOjb = redisUtils.hGet(APP_PREFIX, appId);
        if (null == tOjb) {
            redisUtils.expire(APP_PREFIX, 0L);
            OpenAuthQuery query = new OpenAuthQuery();
            query.setAppId(appId);
            ResponseResult<OpenAuthDTO> responseResult = globalClient.queryOpenAuthOne(query);
            DataUtils.trueThrowException(!ResponseCodeEnum.SUCCESS.getCode().equals(responseResult.getCode()), responseResult.getMsg());
            OpenAuthDTO wxOpenAuthDTO = responseResult.getData();
            if (wxOpenAuthDTO != null) {
                Long tenantId = wxOpenAuthDTO.getTenantId();
                if (tenantId == null) return 0L;
                redisUtils.hSet(APP_PREFIX, appId, tenantId, RedisKeyConstant.MONTH);
                return tenantId;
            }else{
                return 0L;
            }
        }else {
            return Long.valueOf(tOjb.toString());
        }
    }

}
